package com.aizuda.encrypt.helper;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.aizuda.encrypt.constant.EncryptConstant;
import com.aizuda.encrypt.entity.ColumnRule;
import com.aizuda.encrypt.entity.FieldInfo;
import com.aizuda.encrypt.entity.TableRule;
import com.aizuda.encrypt.enums.EncryptType;
import com.aizuda.encrypt.util.RuleUtil;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * 解密工具包
 *
 * @author nn200433
 * @date 2024-03-25 05:16:49
 */
@Slf4j
public class DecryptToolKit {

    public final Map<Class<?>, Boolean>               DECRYPT_TABLE;
    public final Map<Class<?>, Map<Field, FieldInfo>> DECRYPT_FIELD;

    /**
     * 解密工具类初始化构造
     *
     * @author nn200433
     */
    public DecryptToolKit() {
        final List<TableInfo>                tableInfos      = TableInfoHelper.getTableInfos();
        final int                            tableSize       = tableInfos.size();
        final Map<String, TableRule>         tableRuleMap    = getTableRule();
        final EncryptType                    encryptType     = getEncryptType();
        final String                         encryptKey      = getEncryptKey();
        Map<Class<?>, Boolean>               decryptTableMap = new HashMap<Class<?>, Boolean>(tableSize);
        Map<Class<?>, Map<Field, FieldInfo>> classMethodMap  = new HashMap<Class<?>, Map<Field, FieldInfo>>(tableSize);
        for (final TableInfo tableInfo : tableInfos) {
            final String    tableName = tableInfo.getTableName();
            final TableRule tableRule = tableRuleMap.get(tableName);
            if (null == tableRule) {
                // 不在规则里，直接跳过
                continue;
            }
            final Map<String, ColumnRule> columnRuleMap = tableRule.getColumns();
            final Class<?>                entityType    = tableInfo.getEntityType();
            final List<TableFieldInfo>    fieldList     = tableInfo.getFieldList();
            Map<Field, FieldInfo>         methodMap     = new HashMap<Field, FieldInfo>(fieldList.size());
            Boolean                       isDecrypt     = Boolean.FALSE;
            for (final TableFieldInfo fieldInfo : fieldList) {
                final String     column     = fieldInfo.getColumn();
                final ColumnRule columnRule = columnRuleMap.get(column);
                if (null == columnRule) {
                    // 不在规则里，直接跳过
                    continue;
                }
                isDecrypt = Boolean.TRUE;
                final Field  field     = fieldInfo.getField();
                final String fieldName = field.getName();
                final String getMethod = StrUtil.upperFirstAndAddPre(fieldName, "get");
                final Method invokeGet = ReflectUtil.getMethodByName(entityType, getMethod);
                final String setMethod = StrUtil.upperFirstAndAddPre(fieldName, "set");
                final Method invokeSet = ReflectUtil.getMethodByName(entityType, setMethod);
                methodMap.put(field, FieldInfo.builder()
                        .name(fieldName)
                        .column(column)
                        .invokeGet(invokeGet)
                        .invokeSet(invokeSet)
                        .tableName(tableName)
                        .tableEntityType(entityType)
                        .rule(columnRule)
                        .encryptType(encryptType)
                        .encryptKey(encryptKey)
                        .build());
            }
            // 塞值
            decryptTableMap.put(entityType, isDecrypt);
            if (CollUtil.isNotEmpty(methodMap)) {
                classMethodMap.put(entityType, methodMap);
            }
        }
        DECRYPT_TABLE = decryptTableMap;
        DECRYPT_FIELD = classMethodMap;
    }

    /**
     * 是否需要解密
     *
     * @param clz clz
     * @return boolean
     * @author nn200433
     */
    public boolean isNeedDecrypt(Class<?> clz) {
        return DECRYPT_TABLE.getOrDefault(clz, Boolean.FALSE);
    }

    /**
     * 获取需要解密的字段
     *
     * @param clz clz
     * @return {@link Map }<{@link Field }, {@link Method }>
     * @author nn200433
     */
    public Map<Field, FieldInfo> getDecryptField(Class<?> clz) {
        return DECRYPT_FIELD.get(clz);
    }

    /**
     * 获取需要设值的方法
     *
     * @param clz clz
     * @return {@link Map }<{@link Field }, {@link Method }>
     * @author nn200433
     */
    public Method getInvokSetMethod(Class<?> clz, Field field) {
        final Map<Field, FieldInfo> fieldMethodMap = getDecryptField(clz);
        Assert.notEmpty(fieldMethodMap, "未找到 {} 对象！", clz);
        final FieldInfo fieldInfo = fieldMethodMap.getOrDefault(field, null);
        Assert.notNull(fieldInfo, "{} 未找到 {} 的设置方法！", clz, field.getName());
        return fieldInfo.getInvokeSet();
    }

    /**
     * 获取加密类型
     *
     * @return {@link EncryptType }
     * @author nn200433
     */
    private EncryptType getEncryptType() {
        return getEnv().getProperty(EncryptConstant.PROPERTIES_ENCRYPT_TYPE_KEY, EncryptType.class);
    }

    /**
     * 获取加密密钥
     *
     * @return {@link String }
     * @author nn200433
     */
    private String getEncryptKey() {
        return getEnv().getProperty(EncryptConstant.PROPERTIES_ENCRYPT_KEY_KEY, String.class);
    }

    /**
     * 获取数据表加密规则
     *
     * @return {@link Map }<{@link String }, {@link TableRule }>
     * @author nn200433
     */
    private Map<String, TableRule> getTableRule() {
        Map<String, TableRule> resultMap = new HashMap<String, TableRule>();
        final Environment                    environment  = getEnv();
        // 1. 尝试读取远程配置
        resultMap.putAll(getRemoteConfig().execute(environment));
        // 2. 本地配置读取
        Map<String, Map<String, ColumnRule>> waitBuildMap = new HashMap<String, Map<String, ColumnRule>>();
        if (environment instanceof StandardEnvironment) {
            final StandardEnvironment         env             = (StandardEnvironment) environment;
            final MutablePropertySources      propertySources = env.getPropertySources();
            final Iterator<PropertySource<?>> iterator        = propertySources.iterator();
            while (iterator.hasNext()) {
                final PropertySource<?> source = iterator.next();
                String                  name   = source.getName();
                // 去除系统配置和系统环境配置
                if (StrUtil.equalsAny(name, StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)) {
                    continue;
                }
                final Object o = source.getSource();
                if (o instanceof Map) {
                    final Map<String, Object> oMap = (Map<String, Object>) o;
                    for (final Map.Entry<String, Object> entry : oMap.entrySet()) {
                        RuleUtil.findTableRule(entry.getKey(), entry.getValue(), waitBuildMap);
                    }
                }
            }
        }
        if (CollUtil.isNotEmpty(waitBuildMap)) {
            for (final Map.Entry<String, Map<String, ColumnRule>> entry : waitBuildMap.entrySet()) {
                final String                  table         = entry.getKey();
                final Map<String, ColumnRule> columnRuleMap = entry.getValue();
                resultMap.put(table, new TableRule(columnRuleMap));
            }
            log.debug("---> [读取配置] 构造结果：{}", resultMap);
        } else {
            // 没用的尝试
            resultMap.putAll(environment.getProperty(EncryptConstant.PROPERTIES_TABLE_RULE_KEY, Map.class, Collections.emptyMap()));
        }
        return resultMap;
    }

    /**
     * 获取远程配置
     *
     * @return {@link RemoteConfigToolKit }
     * @author nn200433
     */
    private RemoteConfigToolKit getRemoteConfig() {
        return SpringUtil.getBean(RemoteConfigToolKit.class);
    }

    /**
     * 获取环境变量
     *
     * @return {@link Environment }
     * @author nn200433
     */
    private Environment getEnv() {
        return SpringUtil.getBean(Environment.class);
    }

}